package agngui.ui;
import agngui.agnmodel.*;
import agngui.event.AGNEvent;
import agngui.event.AGNListener;
import agngui.event.AGNPublisher;
import agngui.event.HandleEvent;
import agngui.event.HandleReleaseEvent;
import agngui.event.HideCounterconeEvent;
import agngui.event.ObjectEvent;
import agngui.event.ObjectSelectEvent;
import agngui.event.ShowCounterconeEvent;
import agngui.tools.*;

import javax.swing.*;

import java.awt.*;
import java.awt.event.*;

import java.util.*;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import java.io.*;

/**
 * Creates a Configuration Window that allows the user to change various
 * parameters of the system
 * 
 * @author Ben Bloom
 * @author Phil Johnson
 *
 */
public class ConfigWindow implements AGNListener {

	/**
	 * the end of line character for the operating system.
	 */
	public static String endl = System.getProperty("line.separator");
	
	/**
	 * the output directory in which to place files
	 */
	private static String directory = "output" + System.getProperty("file.separator") + "run" + ScatteringGUI.file_separator;
	
	/**
	 * the name of the executable file
	 */
	private static String executable_name = "wind_fun";

	/**
	 * The strings that are common among all the AGN Objects
	 */
	public static String[] common_strings = new String[] {
        "Inner Radius: ", 
        "Outer Radius: ", 
        "Polar angle from system axis: ",
        "Azimuthal angle from system axis: ",
        "Half-opening angle: ",
    };
	
	/**
	 * The label strings which are specific to the Disk
	 */
	public static String[] disk_strings = new String[]{
		common_strings[0],
        common_strings[1],
        "Tangential velocity at inner radius: ",
        "Radial velocity at inner radius: ",
        "Velocity polar angle from disk surface: ",
        "Number of Gaussian components: ",
        "Amplitude of Gaussian component: ",
        "Wavelength of center of Gaussian component: ",
        "Intrinsic width of Gaussian component: ",
        "Radial power dependence: ",
        "Radial emissivity power law index: ",
        "Height/width factor: ",
     };
	
	/**
	 * The label strings which are specific to the Torus
	 */
	public static String[] torus_strings = new String[]{
         "Equatorial Extinction: ",
         "Electron/Dust ratio: ",
         "Power of the Number density radial distribution: ",
         "Number density sine power: ",
         "Number density scale height: ",
    };
	
	/**
	 * The label strings which are specific to the Equatorial Region
	 */
	public static String[] equatorial_strings = new String[]{
         "Number density at inner radius: ",
         "Radial Number density power law exponent: ",
         "Rotational velocity of scatterers at inner radius: ",
         "Radial velocity of scatterers at inner radius: ",
    };
	
	/**
	 * The label strings which are specific to the Wind region
	 */
	public static String[] wind_strings = new String[]{
         "Half-opening angle of the lower wind surface: ",
         "Electron number density at inner radius: ",
         "Radial number density power law exponent: ",
         "Rotational velocity of scatterers at inner radius: ",
         "Wind velocity at inner radius: ",
         "Polar angle of the wind velocity to the wind axis: ",
    };
	
	/**
	 * The label strings which are specific to the Scattering Cone
	 */
	public static String[] cone_strings = new String[]{
         "Number density at inner radius: ",
         "Power of the radial dependence of the density distribution: ",
         "Radial velocity of scatterers at inner radius: ",
    };
	
	/**
	 * The label strings for the Dichroic Polarization components
	 */
	public static String[] dichroic_strings = new String[]{
         "The dichroic polarization w.r.t. the normal to the system axis angle: ",
         "The dichroic efficiency: ",
    };
	
	/**
	 * The label strings for the Dust
	 */
	public static String[] dust_strings = new String[]{
         "The real component of the dust refractive index: ",
         "The imaginary component of the dust refractive index: ",
         "The dust particle diameter: ",
    };
	
	/**
	 * The label strings for the Wavelength
	 */
	public static String[] wavelength_strings = new String[]{
         "Start Wavelength: ",
		 "End Wavelength: ",
		 "Wavelength Step: ",
    };
	
	/**
	 * The label strings for the image specification
	 */
	public static String[] image_strings = new String[]{
         "Distance of the object in Mpc: ",
         "Image pixel scale in arcsec: ",
         "Half x-dimension: ",
         "Half y-dimension: ",
    };
	
	/**
	 * The label strings for the System
	 */
	public static String[] system_strings = new String[]{
		"Inclination of the system polar axis to the line of sight: ",
	};
	
	/**
	 * The names of the objects and tabs and RegionConfigPanels in the system.
	 */
	public static String[] panel_names = new String[]{
		"Emission Disk",
		"Torus",
		"Equatorial",
		"Wind",
		"Cone",
		"Dichroic",
		"Dust",
		"Wavelength",
		"Image",
		"System"
	};
		
	/**
	 * an array of constraints on all of the fields.
	 * These constraints are implemented from Young's windfundat program specification.
	 */
	private Constraint[] constraints = new Constraint[] {
		/* Emission Disk */new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[0]);
				//the disk needs to grab the field map instead of the value map
				//because of the special cases of h[], lc[] and lb[]
				Map<String, Field> field_map = panel.getFieldMap();
				double inner_radius = Double.parseDouble(field_map.get(disk_strings[0].trim()).getValue());
				double outer_radius = Double.parseDouble(field_map.get(disk_strings[1].trim()).getValue());
				
				if(inner_radius >= outer_radius) {
					buf.append("Disk :: inner radius must be less than outer radius" + endl);
				}
				if(outer_radius <= inner_radius) {
					buf.append("Disk :: outer radius must be greater than inner radius" + endl);
				}
				
				double val = Double.parseDouble(field_map.get(disk_strings[2].trim()).getValue());
				if(val >= 1) {
					buf.append("Disk :: tangential velocity must be less than 1" + endl);
				}
				val = Double.parseDouble(field_map.get(disk_strings[3].trim()).getValue());
				if(val >= 1) {
					buf.append("Disk :: radial velocity must be less than 1" + endl);
				}
				
				val = Double.parseDouble(field_map.get(disk_strings[4].trim()).getValue());
				if(val < 0 || val > 180) {
					buf.append("Disk :: polar angle to disk surface must be 0 <= angle <= 180" + endl);
				}
				
				double ng = Double.parseDouble(field_map.get(disk_strings[5].trim()).getValue());
				if(ng != 0) {
					Field lc_field = field_map.get(disk_strings[7].trim());
					String[] h = field_map.get(disk_strings[6].trim()).getValue().split(";");
					String[] lc = lc_field.getValue().split(";");
					String[] lb = field_map.get(disk_strings[8].trim()).getValue().split(";");
					
					if(h.length != ng) {
						buf.append("Disk :: ng=" + ng + " but there are " + h.length + " Gaussian amplitudes" + endl);
					} else {
						for(String component : h) {
							val = Double.parseDouble(component);
							if(val <= 0) {
								buf.append("Disk :: all components of 'h' must be greater than zero" + endl);
							}
						}
					}
					
					RegionConfigPanel wave_panel = config_panels[7];
					double lstart = wave_panel.getValueMap().get(wavelength_strings[0].trim());
					double lend = wave_panel.getValueMap().get(wavelength_strings[1].trim());
					//System.out.println(lstart + " " + lend);
					if(lc.length != ng) {
						buf.append("Disk :: ng=" + ng + " but there are " + lc.length + " Gaussian centers" + endl);
					} else {
						for(String component : lc) {
							val = Double.parseDouble(component);
							val = Converter.convertUnits(val, lc_field.getUnits(), Field.getDefaultUnit(lc_field.getUnitType()));
							if(val < lstart || val > lend) {
								buf.append("Disk :: all components of 'lc' must be lstart <= lc <= lend, (" + val + ") is invalid" + endl);
							}
						}
					}
					
					if(lb.length != ng) {
						buf.append("Disk :: ng=" + ng + " but there are " + lb.length + " Gaussian widths" + endl);
					} else {
						for(String component : lb) {
							val = Double.parseDouble(component);
							if(val <= 0) {
								buf.append("Disk :: all components of 'lb' must be greater than zero" + endl);
							}
						}
					}
				}
				val = Double.parseDouble(field_map.get(disk_strings[10].trim()).getValue());
				if(val < 0) {
					buf.append("Disk :: Emissivity power law index must be >= zero" + endl);
				}
				
				val = Double.parseDouble(field_map.get(disk_strings[11].trim()).getValue());
				if(val < 0) {
					buf.append("Disk :: Height and width factor must be >= zero" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Torus */		new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[1]);
				Map<String, Double> value_map = panel.getValueMap();
				
				
				double inner_radius = value_map.get(common_strings[0].trim());
				double outer_radius = value_map.get(common_strings[1].trim());
				
				if(inner_radius >= outer_radius) {
					buf.append("Torus :: inner radius must be less than outer radius" + endl);
				}
				if(outer_radius <= inner_radius) {
					buf.append("Torus :: outer radius must be greater than inner radius" + endl);
				}
				
				double polar_angle = value_map.get(common_strings[2].trim());
				if(polar_angle > 180 || polar_angle < 0) {
					buf.append("Torus :: polar angle of torus to system axis must be 0 <= theta <= 180" + endl);
				}
				
				double azimuthal_angle = value_map.get(common_strings[3].trim());
				if(azimuthal_angle > 360 || azimuthal_angle < 0) {
					buf.append("Torus :: azimuthal angle of torus to system axis must be 0 <= theta <= 360" + endl);
				}
				
				double half_opening_angle = value_map.get(common_strings[4].trim());
				if(half_opening_angle > 90 || half_opening_angle < 0) {
					buf.append("Torus :: half opening angle must be 0 <= theta <= 90" + endl);
				}
				
				double val = value_map.get(torus_strings[0].trim());
				if(which_region_checks[0].isSelected() && val < 0) {
					buf.append("Torus :: Equatorial Extinction must be >= 0" + endl);
				}
				
				val = value_map.get(torus_strings[1].trim());
				if(val < 0) {
					buf.append("Torus :: Electron/Dust ratio must be >= 0" + endl);
				}
				
				val = value_map.get(torus_strings[2].trim());
				if(val < 0) {
					buf.append("Torus :: Power of the Number density radial distribution ratio must be >= 0" + endl);
				}
				
				val = value_map.get(torus_strings[3].trim());
				if(val < 0) {
					buf.append("Torus :: Number density sine power must be >= 0" + endl);
				}
				
				val = value_map.get(torus_strings[4].trim());
				if(val < 0) {
					buf.append("Torus :: Number density scale height must be >= 0" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Equatorial Region */new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[2]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double inner_radius = value_map.get(common_strings[0].trim());
				double outer_radius = value_map.get(common_strings[1].trim());
				
				if(inner_radius >= outer_radius) {
					buf.append("Equatorial :: inner radius must be less than outer radius" + endl);
				}
				if(outer_radius <= inner_radius) {
					buf.append("Equatorial :: outer radius must be greater than inner radius" + endl);
				}
				
				double polar_angle = value_map.get(common_strings[2].trim());
				if(polar_angle > 180 || polar_angle < 0) {
					buf.append("Equatorial :: polar angle of equatorial to system axis must be 0 <= theta <= 180" + endl);
				}
				
				double azimuthal_angle = value_map.get(common_strings[3].trim());
				if(azimuthal_angle > 360 || azimuthal_angle < 0) {
					buf.append("Equatorial :: azimuthal angle of equatorial to system axis must be 0 <= theta <= 360" + endl);
				}
				
				double half_opening_angle = value_map.get(common_strings[4].trim());
				if(half_opening_angle > 90 || half_opening_angle < 0) {
					buf.append("Equatorial :: half opening angle must be 0 <= theta <= 90" + endl);
				}
				
				double val = value_map.get(equatorial_strings[0].trim());
				if(which_region_checks[1].isSelected() && val < 0) {
					buf.append("Equatorial :: Number Density at inner radius must be >= 0" + endl);
				}
				
				val = value_map.get(equatorial_strings[1].trim());
				if(val < 0) {
					buf.append("Equatorial :: Radial Number Density power law exponent must be >= 0" + endl);
				}
				
				val = value_map.get(equatorial_strings[2].trim());
				if(val >= 1) {
					buf.append("Equatorial :: Rotational velocity at inner radius must be < 1" + endl);
				}
				
				val = value_map.get(equatorial_strings[3].trim());
				if(val >= 1) {
					buf.append("Equatorial :: Radial velocity at inner radius must be < 1" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Wind */		new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[3]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double inner_radius = value_map.get(common_strings[0].trim());
				double outer_radius = value_map.get(common_strings[1].trim());
				
				if(inner_radius >= outer_radius) {
					buf.append("Wind :: inner radius must be less than outer radius" + endl);
				}
				if(outer_radius <= inner_radius) {
					buf.append("Wind :: outer radius must be greater than inner radius" + endl);
				}
				
				double polar_angle = value_map.get(common_strings[2].trim());
				if(polar_angle > 180 || polar_angle < 0) {
					buf.append("Wind :: polar angle of wind to system axis must be 0 <= theta <= 180" + endl);
				}
				
				double azimuthal_angle = value_map.get(common_strings[3].trim());
				if(azimuthal_angle > 360 || azimuthal_angle < 0) {
					buf.append("Wind :: azimuthal angle of wind to system axis must be 0 <= theta <= 360" + endl);
				}
				
				double half_opening_upper = value_map.get(common_strings[4].trim());
				double half_opening_lower = value_map.get(wind_strings[0].trim());
				if(half_opening_lower < half_opening_upper || half_opening_lower < 0) {
					buf.append("Wind :: half opening lower must be 0 <= theta < half opening upper" + endl);
				}
				if(half_opening_upper > 180 || half_opening_upper < 0) {
					buf.append("Wind :: half opening upper must be half_opening_lower < theta <= 180" + endl);
				}
				
				double val = value_map.get(wind_strings[1].trim());
				if(which_region_checks[2].isSelected() && val < 0) {
					buf.append("Wind :: Number Density at inner radius must be >= 0" + endl);
				}
				
				val = value_map.get(wind_strings[2].trim());
				if(val < 0) {
					buf.append("Wind :: Radial Number Density power law exponent must be >= 0" + endl);
				}
				
				
				
				val = value_map.get(wind_strings[3].trim());
				if(val >= 1) {
					buf.append("Wind :: Rotational velocity at inner radius must be < 1" + endl);
				}
				
				val = value_map.get(wind_strings[4].trim());
				if(val >= 1) {
					buf.append("Wind :: Radial velocity at inner radius must be < 1" + endl);
				}
				
				val = value_map.get(wind_strings[5].trim());
				if(val < 0 || val > 90) {
					buf.append("Wind :: Polar angle of wind velocity to wind axis must be 0 <= theta <= 90" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Cone */		new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[4]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double inner_radius = value_map.get(common_strings[0].trim());
				double outer_radius = value_map.get(common_strings[1].trim());
				
				if(inner_radius >= outer_radius) {
					buf.append("Cone :: inner radius must be less than outer radius" + endl);
				}
				if(outer_radius <= inner_radius) {
					buf.append("Cone :: outer radius must be greater than inner radius" + endl);
				}
				
				double polar_angle = value_map.get(common_strings[2].trim());
				if(polar_angle > 180 || polar_angle < 0) {
					buf.append("Cone :: polar angle of cone to system axis must be 0 <= theta <= 180" + endl);
				}
				
				double azimuthal_angle = value_map.get(common_strings[3].trim());
				if(azimuthal_angle > 360 || azimuthal_angle < 0) {
					buf.append("Cone :: azimuthal angle of cone to system axis must be 0 <= theta <= 360" + endl);
				}
				
				double half_opening_angle = value_map.get(common_strings[4].trim());
				if(half_opening_angle > 90 || half_opening_angle < 0) {
					buf.append("Cone :: half opening angle must be 0 <= theta <= 90" + endl);
				}
				
				double val = value_map.get(cone_strings[0].trim());
				if(which_region_checks[2].isSelected() && val < 0) {
					buf.append("Cone :: Number Density at inner radius must be >= 0" + endl);
				}
				
				val = value_map.get(cone_strings[1].trim());
				if(val < 0) {
					buf.append("Cone :: Power of radial dependence on the density distribution must be >= 0" + endl);
				}
				
				val = value_map.get(cone_strings[2].trim());
				if(val >= 1) {
					buf.append("Cone :: Radial velocity at inner radius must be < 1" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Dichroic */	new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[5]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double val = value_map.get(dichroic_strings[0].trim());
				if(val < 0 || val > 180) {
					buf.append("Dichroic :: Dichroic polarization angle must be 0 <= theta <= 180" + endl);
				}
				
				val = value_map.get(dichroic_strings[1].trim());
				if(val <= 0 || val > 100) {
					buf.append("Dichroic :: Dichroic efficiency must be 0 < eff <= 100" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Dust */		new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[6]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double val = value_map.get(dust_strings[0].trim());
				if(val <= 0) {
					buf.append("Dust :: The real component must be greater than zero" + endl);
				}
				val = value_map.get(dust_strings[1].trim());
				if(val < 0) {
					buf.append("Dust :: The imaginary component must be >= 0" + endl);
				}
				
				val = value_map.get(dust_strings[2].trim());
				if(val <= 0) {
					buf.append("Dust :: The particle diameter must be greater than zero" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Wavelength */new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[7]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double lstart = value_map.get(wavelength_strings[0].trim());
				double lend = value_map.get(wavelength_strings[1].trim());
				double lstep = value_map.get(wavelength_strings[2].trim());
				if(lstart <= 0 || lstart > lend) {
					buf.append("Wavelength :: must have 0 < lstart <= lend" + endl);
				}
				
				if(lstep <= 0) {
					buf.append("Wavelength :: must have lstep > 0" + endl);
				}
				
				return buf.toString();
			}
		},
		/* Image */		new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[8]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double dist = value_map.get(image_strings[0].trim());
				double pix = value_map.get(image_strings[1].trim());
				double xdim = value_map.get(image_strings[2].trim());
				double ydim = value_map.get(image_strings[3].trim());
				
				if(dist <= 0) {
					buf.append("Image :: distance of object must be > 0" + endl);
				}
				
				if(pix <= 0) {
					buf.append("Image :: image pixel scale must be > 0" + endl);
				}
				
				if(xdim < 0) {
					buf.append("Image :: x-dimension must be >= 0" + endl);
				}
				
				if(ydim < 0) {
					buf.append("Image :: y-dimension must be >= 0" + endl);
				}
				
				return buf.toString();
			}
		},
		/* System */	new Constraint() {
			public String checkConstraints() {
				StringBuilder buf = new StringBuilder("");
				RegionConfigPanel panel = getPanel(panel_names[9]);
				Map<String, Double> value_map = panel.getValueMap();
				
				double val = value_map.get(system_strings[0].trim());
				if(val < 0 || val > 180) {
					buf.append("System :: inclination must be 0 <= inc <= 180" + endl);
				}
				
				return buf.toString();
			}
		}
	};
	
	/**
	 * the container for the configuration UI
	 */
	private JFrame config_window;
	
	/**
	 * the configuration panels in the system
	 */
	private RegionConfigPanel[] config_panels;
	
	/**
	 * the checkboxes denoting which regions should be included
	 */
	private JCheckBoxMenuItem[] which_region_checks;

	/**
	 * the container which holds the configuration panels
	 */
    private JTabbedPane main_config_pane;
    
    /**
     * a mapping from region names to the scroll panes which hold them.
     * This is used to figure out which regions are which when selecting objects in the main UI 
     */
	private Map<String, JScrollPane> scroll_map;
	
	/**
	 * The status field that should say what is currently going on when things are being automated
	 */
	private JTextField status_field;
	
	/**
	 * The progress bar showing how much progress has been made in generating and/or running files
	 */
	private JProgressBar progress_bar;
	
	/**
	 * A text field showing how many total files there will be generated from the given configuration set
	 */
	private JTextField total_files;
	
	/**
	 * A text field showing how many files the "current" panel will generate with its configuration
	 */
	private JTextField files_this_panel;

	/**
	 * constructs a new ConfigWindow
	 * 
	 * @param status A status bar, used to tell what's happening
	 * @param progress A progress bar, records how far along in file processing the program currently is
	 * @param displayer Contains a method for showing the objects in the scene 
	 */
	public ConfigWindow(JTextField status, JProgressBar progress, final ObjectDisplayer displayer) {
		config_window = new JFrame("Configuration");
		config_window.setSize(1024,480);
		config_window.setLocationByPlatform(true);
		
		this.status_field = status;
		this.progress_bar = progress;
	
		config_panels = new RegionConfigPanel[10];
		scroll_map = new HashMap<String, JScrollPane>();
        //emission_disk_config_panel 
		config_panels[0] = new RegionConfigPanel(panel_names[0], 
             disk_strings, false, 
			 new boolean[]{  true,               true,               false,              true,        
			                 true,               false,              false,              false, 
							 false,              false,              false,              true}, 
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE, UNIT_TYPE.VELOCITY, UNIT_TYPE.VELOCITY, 
			                 UNIT_TYPE.ANGLE,    UNIT_TYPE.NONE,     UNIT_TYPE.NONE,     UNIT_TYPE.DISTANCE, 
							 UNIT_TYPE.DISTANCE, UNIT_TYPE.NONE,     UNIT_TYPE.NONE,     UNIT_TYPE.NONE});

        //torus_config_panel 
		config_panels[1] = new RegionConfigPanel(panel_names[1], 
             torus_strings, true,
			 new boolean[]{  true,               true,               true,            true, 
			                 true,               true,               false,           false,
							 true,               true},
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE, UNIT_TYPE.ANGLE, UNIT_TYPE.ANGLE, 
			                 UNIT_TYPE.ANGLE,    UNIT_TYPE.NONE,     UNIT_TYPE.NONE,  UNIT_TYPE.NONE, 
							 UNIT_TYPE.NONE,     UNIT_TYPE.DISTANCE});

        //equatorial_config_panel 
		config_panels[2] = new RegionConfigPanel(panel_names[2], 
             equatorial_strings, true,
			 new boolean[]{  true,               true,               true,            true, 
						     true,               true,               false,           false,
							 true},
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE, UNIT_TYPE.ANGLE, UNIT_TYPE.ANGLE, 
			                 UNIT_TYPE.ANGLE,    UNIT_TYPE.DENSITY,  UNIT_TYPE.NONE,  UNIT_TYPE.VELOCITY, 
							 UNIT_TYPE.VELOCITY});
        //wind_config_panel 
		config_panels[3] = new RegionConfigPanel(panel_names[3], 
             wind_strings, true,
			 new boolean[]{  true,               true,               true,              true, 
			                 true,               true,               true,              false, 
							 false,              true,               true},
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE, UNIT_TYPE.ANGLE,   UNIT_TYPE.ANGLE, 
			                 UNIT_TYPE.ANGLE,    UNIT_TYPE.ANGLE,    UNIT_TYPE.DENSITY, UNIT_TYPE.NONE, 
							 UNIT_TYPE.VELOCITY, UNIT_TYPE.VELOCITY, UNIT_TYPE.ANGLE});
        //cone_config_panel 
		config_panels[4] = new RegionConfigPanel(panel_names[4], 
             cone_strings, true, 
			 new boolean[]{  true,               true,               true,            true, 
			                 true,               true,               false,           true},
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE, UNIT_TYPE.ANGLE, UNIT_TYPE.ANGLE, 
			                 UNIT_TYPE.ANGLE,    UNIT_TYPE.DENSITY,  UNIT_TYPE.NONE,  UNIT_TYPE.VELOCITY});
        //dichroic_config_panel 
		config_panels[5] = new RegionConfigPanel(panel_names[5], 
             dichroic_strings, false,
			 new boolean[]{  true,            true},
			 new UNIT_TYPE[]{UNIT_TYPE.ANGLE, UNIT_TYPE.NONE});
        //dust_config_panel 
		config_panels[6] = new RegionConfigPanel(panel_names[6], 
             dust_strings, false,
			 new boolean[]{  false,          false,          true},
			 new UNIT_TYPE[]{UNIT_TYPE.NONE, UNIT_TYPE.NONE, UNIT_TYPE.DISTANCE});
        //wavelength_config_panel 
		config_panels[7] = new RegionConfigPanel(panel_names[7], 
             wavelength_strings, false,
			 new boolean[]{  false,              false,              false},
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE, UNIT_TYPE.DISTANCE});
        //image_config_panel 
		config_panels[8] = new RegionConfigPanel(panel_names[8], 
             image_strings, false,
			 new boolean[]{  false,              false,           false,          false},
			 new UNIT_TYPE[]{UNIT_TYPE.DISTANCE, UNIT_TYPE.ANGLE, UNIT_TYPE.NONE, UNIT_TYPE.NONE});
		config_panels[9] = new RegionConfigPanel(panel_names[9],
			 system_strings, false,
			 new boolean[]{  true           },
			 new UNIT_TYPE[]{UNIT_TYPE.ANGLE});
		
		final JCheckBoxMenuItem cone_check = new JCheckBoxMenuItem("Show", false);
		final JCheckBoxMenuItem counter_cone_check = new JCheckBoxMenuItem("Show Counter-cone", false);
        which_region_checks = new JCheckBoxMenuItem[]{
            new JCheckBoxMenuItem("Show", false),
            new JCheckBoxMenuItem("Show", false),
            new JCheckBoxMenuItem("Show", false),
            cone_check,
            counter_cone_check,
        };
        for(JCheckBoxMenuItem box : which_region_checks) {
        	box.setMnemonic(KeyEvent.VK_S);
        }
		counter_cone_check.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(!cone_check.isSelected() && counter_cone_check.isSelected()) {
					cone_check.setSelected(true);
				}
				displayer.displayObject("Cone", cone_check.isSelected());
				if(counter_cone_check.isSelected()) {
					AGNPublisher.dispatchEvent(new ShowCounterconeEvent());
				} else {
					AGNPublisher.dispatchEvent(new HideCounterconeEvent());
				}
			}
		});
		cone_check.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(counter_cone_check.isSelected() && !cone_check.isSelected()) {
					counter_cone_check.setSelected(false);
				}
				displayer.displayObject("Cone", cone_check.isSelected());
			}
		});
		which_region_checks[0].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				displayer.displayObject("Torus", which_region_checks[0].isSelected());
			}
		});
		which_region_checks[1].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				displayer.displayObject("Equatorial", which_region_checks[1].isSelected());
			}
		});
		which_region_checks[2].addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				displayer.displayObject("Wind", which_region_checks[2].isSelected());
			}
		});
	
		Container config_cpane = config_window.getContentPane();
		config_cpane.setLayout(new BorderLayout());
        main_config_pane = new JTabbedPane();
		
        for(int i = 0; i < config_panels.length; i++) {
			RegionConfigPanel panel = config_panels[i];
			JScrollPane scroller = new JScrollPane(panel);
			config_panels[i].setConstraint(constraints[i]);
			scroll_map.put(panel.getName(), scroller);
			main_config_pane.addTab(panel.getName(), scroller);
		}
        
		total_files = new JTextField(15);
		files_this_panel = new JTextField(15);
		total_files.setEditable(false);
		files_this_panel.setEditable(false);
        
		JPanel status_pane = new JPanel(new GridLayout(1, 0));
		status_pane.add(total_files);
		status_pane.add(files_this_panel);
		
		config_cpane.add(main_config_pane, BorderLayout.CENTER);
		config_cpane.add(status_pane, BorderLayout.SOUTH);
		
		
		try {
			loadDefaultConfiguration();
		} catch(IOException ioe) {
			error("Could not load default configuration... " + ioe.getMessage());
		}
		
		initializeMenuBar();
		config_window.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		config_window.setVisible(true);
	}

	/**
	 * gets the checkbox associated with the given ID, if it exists
	 * 
	 * @param id The identifier of the check box
	 * 
	 * @return the region check box associated with the given id
	 */
	JCheckBoxMenuItem getRegionCheckbox(String id) {
		JCheckBoxMenuItem region_box = null;
		if(id.equals("Torus")) {
			region_box = which_region_checks[0];
		} else if(id.equals("Equatorial")) {
			region_box = which_region_checks[1];
		} else if(id.equals("Wind")) {
			region_box = which_region_checks[2];
		} else if(id.equals("Cone")) {
			region_box = which_region_checks[3];
		} else if(id.equals("Counter-cone")) {
			region_box = which_region_checks[4];
		}	
		return region_box;
	}
	
	/**
	 * sets the region with the given ID to be shown
	 * 
	 * @param id The ID of the region to become active
	 * @param active true if the region should be shown, false if it should be hidden
	 */
	public void setRegion(String id, boolean active) { 
		if(id.equals("Torus")) {
			which_region_checks[0].setSelected(active); 
		} else if(id.equals("Equatorial")) {
			which_region_checks[1].setSelected(active); 
		} else if(id.equals("Wind")) {
			which_region_checks[2].setSelected(active); 
		} else if(id.equals("Cone")) {
			which_region_checks[3].setSelected(active); 
		}
	}
	
	/**
	 * implements AGNListener.processEvent(AGNEvent)
	 * 
	 * processes events in the system that are relevant to the ConfigWindow
	 * 
	 * @param event The event which was generated and requires processing
	 */
	public void processEvent(AGNEvent event) {
		if(event instanceof ObjectSelectEvent) {
			AGNObject object = ((ObjectEvent)event).getObject();
			focusPanel(object.getIdentifier());
		} else if(event instanceof HandleReleaseEvent) {
			//System.out.println("ConfigWindow.processEvent(): caught HandleReleaseEvent");
			AGNParameterHandle handle = ((HandleEvent)event).getHandle();
			AGNObject object = handle.getObject();
			String field_id = handle.getIdentifier();
			RegionConfigPanel panel = getPanel(object.getIdentifier());
			//System.out.println(object.getIdentifier() + " " + field_id + " " + panel.getName());
			
			Field the_field = panel.getField(field_id);
			//System.out.println(the_field.toString());
			String old_value = the_field.getValue();
			
			//System.out.println("Adding " + panel.getName() + " " + the_field.getName());
			String from_units = Field.getDefaultUnit(the_field.getUnitType());
			if(field_id.indexOf("angle") != -1) {
				from_units = "rad";
			}
			String to_units = the_field.getUnits();
			
			double value = handle.getRecentValue();
			
			double converted = Converter.convertUnits(value, from_units, to_units);
			//System.out.println("Convert " + value + " from " + from_units + " to " + to_units + " == " + converted);
			the_field.setValue(converted);
			String problems = panel.checkConstraints();
			if(!problems.equals("")) {
				the_field.setValue(old_value);
			}
		}
	}
	
	/**
	 * says whether or not the torus should be shown
	 * 
	 * @return true if the torus should be shown
	 */
	public boolean doTorus() { return which_region_checks[0].isSelected(); }
	
	/**
	 * says whether or not the equatorial region should be shown
	 * 
	 * @return true if the equatorial region should be shown
	 */
	public boolean doEquatorial() { return which_region_checks[1].isSelected(); }
	
	/**
	 * says whether or not the wind should be shown
	 * 
	 * @return true if the Wind should be shown
	 */
	public boolean doWind() { return which_region_checks[2].isSelected(); }
	
	/**
	 * says whether or not the cone should be shown
	 * 
	 * @return true if the Cone should be shown
	 */
	public boolean doCone() { return which_region_checks[3].isSelected(); }
	
	/**
	 * says whether or not the countercone should be shown
	 * 
	 * @return true if the Countercone should be shown
	 */
	public boolean doCountercone() { return which_region_checks[4].isSelected(); }
	
	/**
	 * returns the name of the active panel
	 * @return the name of the active panel
	 */
	public String getActivePanelName() { return ((RegionConfigPanel)(main_config_pane.getSelectedComponent().getComponentAt(10,10).getComponentAt(10,10))).getName(); }
	
	/**
	 * returns the RegionConfigPanel with the given name
	 * @param name The panel to grab
	 * @return the panel with the given name
	 */
	public RegionConfigPanel getPanel(String name) {
		return (RegionConfigPanel)(scroll_map.get(name).getComponentAt(10,10).getComponentAt(10,10));
	}
	
	/**
	 * Given a panel's name, bring that panel into focus
	 * @param name the name of the panel to focus
	 */
	public void focusPanel(String name) {
		RegionConfigPanel panel = getPanel(name);
		if(panel == null) {
			throw new NullPointerException("focusPanel(): \"" + name + "\" is not a valid Config Panel name");
		}
		config_window.requestFocus();
		main_config_pane.setSelectedComponent(scroll_map.get(name));
	}
	
	/**
	 * Given a field id and a panel, verify that the field's value is valid
	 *  
	 * @param id field ID to check
	 * @param panel Panel that the field resides in
	 * @return the value contained in the field
	 * @throws IllegalArgumentException if the value in the field is not a valid number
	 */
	private double value_check(String id, RegionConfigPanel panel) throws IllegalArgumentException {
		Field field = panel.getField(id.trim());
		double value = 0;
		try {
			field.verifyNumbers();
			value = Double.parseDouble(field.getValue());
		} catch(NumberFormatException nfe) {
			config_window.requestFocus();
			main_config_pane.setSelectedComponent(panel);
			field.grabFocus();
			String panel_name = panel.getName();
			throw new IllegalArgumentException(panel_name + " :: " + id + " must be a real value");
		}
		return value;
	}
	
	/**
	 * convert all field values to the values they need to be.  Which just
	 * converts from one unit to another (parsecs to meters, for example)
	 */
	private void doConversions() {
		for(RegionConfigPanel panel : config_panels) {
			try {
				panel.verifyFields();
				if(panel == config_panels[0]) {//special case for Disk panel
					Map<String, Field> field_map = panel.getFieldMap();
					
					Field h_field = field_map.get(disk_strings[6].trim());
					Field lc_field = field_map.get(disk_strings[7].trim());
					Field lb_field = field_map.get(disk_strings[8].trim());
					
					Collection<Field> fields = field_map.values();
					for(Field field : fields) {
						if(field == h_field || field == lc_field || field == lb_field) {
							continue;
						}
						field.convertUnits();
					}
					
					//don't actually need to convert the h_field.
					String lc_text = lc_field.getValue();
					if(!lc_text.equals("")) {
						StringBuilder buffer = new StringBuilder("");
						String [] components = lc_text.split(";");
						String lc_unit = lc_field.getUnits();
						int len = components.length;
						for(int i = 0; i < len; i++) {
							double val = Double.parseDouble(components[i]);
							double converted = Converter.convertUnits(val, lc_unit, "a0");//default to angstroms
							buffer.append(Double.toString(converted));
							if(i < len-1) {
								buffer.append(";");
							}
						}
						lc_field.setUnits("a0");
						lc_field.setValue(buffer.toString());
					}
					String lb_text = lb_field.getValue();
					if(!lb_text.equals("")) {
						StringBuilder buffer = new StringBuilder("");
						String [] components = lb_text.split(";");
						String lb_unit = lb_field.getUnits();
						int len = components.length;
						for(int i = 0; i < len; i++) {
							double val = Double.parseDouble(components[i]);
							double converted = Converter.convertUnits(val, lb_unit, "a0");//default to angstrom
							buffer.append(Double.toString(converted));
							if(i < len-1) {
								buffer.append(";");
							}
						}
						lb_field.setUnits("a0");
						lb_field.setValue(buffer.toString());
					}
				} else if(panel == config_panels[8]) {//special case for Image panel
					Field field = panel.getField(image_strings[0].trim());
					field.convertUnits("Mpc");
					field = panel.getField(image_strings[1].trim());
					field.convertUnits("arcsec");
				} else if(panel == config_panels[7]) {//special case for Wavelength panel
					Field field = panel.getField(wavelength_strings[0].trim());
					field.convertUnits("a0");
					field = panel.getField(wavelength_strings[1].trim());
					field.convertUnits("a0");
					field = panel.getField(wavelength_strings[2].trim());
					field.convertUnits("a0");
				} else {
					panel.doConversions();
				} 
			} catch(IllegalArgumentException iae) {
				focusPanel(panel.getName());
				throw iae;
			}
		}
	}
	
	
	/**
	 * Creates the output file from this program, which will be an input file to
	 * Young's windfun program
	 * 
	 * @param file The file that will contain the output information
	 * @throws IOException if an IOException occurs
	 * @throws IllegalArgumentException if a field parameter is not valid
	 */
	private void generateOutputFile(File file) throws IOException, IllegalArgumentException {
		StringBuilder errors = new StringBuilder("");
		for(RegionConfigPanel panel : config_panels) {
			String str = panel.checkConstraints();
			if(!str.equals("")) {
				//System.out.println("." + str + ".");
				errors.append(str + endl);
			}
		}
		
		if(errors.length() > 0) {
			status_field.setText("Encountered an error!");
			config_window.requestFocus();
			error(errors.toString());
			return;
		}
		
		List<Number> values = new LinkedList<Number>();
		
		//progress_bar.setIndeterminate(true);
		
		RegionConfigPanel panel = config_panels[9];//system panel is now at the end.
		values.add(value_check(system_strings[0], panel));
		
		
		panel = config_panels[0];//disk panel
		//add up to ng
		for(int i = 0; i < 5; i++) {
			values.add(value_check(disk_strings[i], panel));
		}
		//we have to do something special with ng and h, lc, lb
		double ng_d = value_check(disk_strings[5], panel);
		int ng = (int)ng_d;
		values.add(ng);
		
		
		if(ng > 0) {
			Map<String, Field> map = panel.getFieldMap();
			
			Field f = map.get(disk_strings[6].trim());
			double[] h = f.verifyList();
			
			int len = (h == null) ? 0 : h.length;
			if(len != ng) {
				throw new IllegalArgumentException(panel.getName() + " :: h[] has " + len + " components, but ng=" + ng);
			}
			
			f = map.get(disk_strings[7].trim());
			double[] lc = f.verifyList();
			len = (lc == null) ? 0 : lc.length;
			if(len != ng) {
				throw new IllegalArgumentException(panel.getName() + " :: lc[] has " + len + " components, but ng=" + ng);
			}
			
			f = map.get(disk_strings[8].trim());
			double[] lb = f.verifyList();
			len = (lb == null) ? 0 : lb.length;
			if(len != ng) {
				throw new IllegalArgumentException(panel.getName() + " :: lb[] has " + len + " components, but ng=" + ng);
			}
			//at this point, h[], lc[], and lb[] are known to have length == ng, so it's good.
			
			for(int i = 0; i < ng; i++) {
				values.add(h[i]);
				values.add(lc[i]);
				values.add(lb[i]);
			}
		}
	
		for(int i = 9; i <= 11; i++) {
			values.add(value_check(disk_strings[i], panel));
		}
		
		panel = config_panels[1];//torus panel
		values.add(value_check(torus_strings[0], panel));
		values.add(value_check(common_strings[0], panel));
		values.add(value_check(common_strings[1], panel));
		values.add(value_check(common_strings[4], panel));
		values.add(value_check(torus_strings[1], panel));
		values.add(value_check(torus_strings[2], panel));
		values.add(value_check(torus_strings[3], panel));
		values.add(value_check(torus_strings[4], panel));
		values.add(value_check(common_strings[2], panel));
		values.add(value_check(common_strings[3], panel));
		
		panel = config_panels[2];//equatorial panel
		values.add(value_check(common_strings[4], panel));
		values.add(value_check(common_strings[0], panel));
		values.add(value_check(common_strings[1], panel));
		values.add(value_check(equatorial_strings[0], panel));
		values.add(value_check(equatorial_strings[1], panel));
		values.add(value_check(common_strings[2], panel));
		values.add(value_check(common_strings[3], panel));
		values.add(value_check(equatorial_strings[2], panel));
		values.add(value_check(equatorial_strings[3], panel));
		
		panel = config_panels[3];//wind panel
		values.add(value_check(common_strings[4], panel));
		values.add(value_check(wind_strings[0], panel));
		values.add(value_check(common_strings[0], panel));
		values.add(value_check(common_strings[1], panel));
		values.add(value_check(wind_strings[1], panel));
		values.add(value_check(wind_strings[2], panel));
		values.add(value_check(common_strings[2], panel));
		values.add(value_check(common_strings[3], panel));
		values.add(value_check(wind_strings[3], panel));
		values.add(value_check(wind_strings[4], panel));
		values.add(value_check(wind_strings[5], panel));
		
		panel = config_panels[4];//cone panel
		values.add(value_check(common_strings[0], panel));
		values.add(value_check(common_strings[1], panel));
		values.add(value_check(common_strings[4], panel));
		values.add(value_check(cone_strings[0], panel));
		values.add(value_check(cone_strings[1], panel));
		values.add(value_check(common_strings[2], panel));
		values.add(value_check(common_strings[3], panel));
		values.add(value_check(cone_strings[2], panel));
		
		panel = config_panels[5];//dichroic panel
		values.add(value_check(dichroic_strings[0], panel));
		values.add(value_check(dichroic_strings[1], panel));
		
		panel = config_panels[6];//dust panel
		values.add(value_check(dust_strings[0], panel));
		values.add(value_check(dust_strings[1], panel));
		values.add(value_check(dust_strings[2], panel));
		
		panel = config_panels[7];//wavelength panel
		values.add(value_check(wavelength_strings[0], panel));
		values.add(value_check(wavelength_strings[1], panel));
		values.add(value_check(wavelength_strings[2], panel));
		
		panel = config_panels[8];//image panel
		values.add(value_check(image_strings[0], panel));
		values.add(value_check(image_strings[1], panel));
		values.add(value_check(image_strings[2], panel));
		values.add(value_check(image_strings[3], panel));
		
		
		PrintStream out = new PrintStream(file);
		
		int size = values.size();
		int cap = size-7;//seven values output after which region is output
		//progress_bar.setIndeterminate(false);
		//progress_bar.setMaximum(size+5);//5 region check boxes
		//int progress_count = 0;
		for(int i = 0; i < cap; i++) {
			out.println(values.get(i));
			//progress_bar.setValue(progress_count++);
		}
		for(JCheckBoxMenuItem check : which_region_checks) {
			if(check.isSelected()) {
				out.println("yes");
			} else {
				out.println("no");
			}
			//progress_bar.setValue(progress_count++);
		}
		for(int i = cap; i < size; i++) {
			out.println(values.get(i));
			//progress_bar.setValue(progress_count++);
		}
		//progress_bar.setValue(progress_count);
		
		out.flush();
		out.close();
	}
	
	/**
	 * saves the current configuration to the given file
	 * 
	 * @param file The file to save the configuration into
	 * @throws IOException if an IOException occurs
	 */
	private void saveConfiguration(File file) throws IOException {
		PrintStream out = new PrintStream(file);
		for(RegionConfigPanel panel : config_panels) {
			out.println("[" + panel.getName() + "]");
			out.println(panel.getConfigurationString());
		}
		out.println("[Which Regions]");
		out.println("Torus: " + which_region_checks[0].isSelected());
		out.println("Equatorial: " + which_region_checks[1].isSelected());
		out.println("Wind: " + which_region_checks[2].isSelected());
		out.println("Cone: " + which_region_checks[3].isSelected());
		out.println("Counter-cone: " + which_region_checks[4].isSelected());
		out.println();
		out.println("[End]");
	}
	
	/**
	 * loads the default configuration file directly from the jar file
	 * 
	 * @throws IOException if an IOException occurs
	 */
	private void loadDefaultConfiguration() throws IOException {
		JarFile jar = new JarFile("AGNGUI.jar");
		JarEntry entry = jar.getJarEntry("bin/configs/default.cfg");
		InputStream istream = jar.getInputStream(entry);
		loadConfiguration(istream);
	}
	
	/**
	 * loads a configuration from the given file
	 * 
	 * @param istream The input stream from which to load the configuration
	 * @throws IOException if an IOException occurs
	 */
	private void loadConfiguration(InputStream istream) throws IOException {
		BufferedReader input = new BufferedReader(new InputStreamReader(istream));
		String read = null;
		//System.out.println(file.getAbsolutePath() + "    " + file.getName());
		
		List<String> entry_buffer = new ArrayList<String>();
		String previous_region = null;
		while((read = input.readLine()) != null) {
			read = read.trim();
			if(read.equals("") || read.startsWith("//") || read.startsWith("#")) {
				continue;
			}
			if(read.indexOf("//") != -1) {
				read = read.substring(0, read.indexOf("//"));
			}
			if(read.indexOf("#") != -1) {
				read = read.substring(0, read.indexOf("#"));
			}
			if(read.indexOf("[") != -1) {
				if(previous_region != null) {
					if(previous_region.equals("[Which Regions]")) {
						for(String str : entry_buffer) {
							String tag = str.substring(0, str.indexOf(":"));
							boolean value = Boolean.parseBoolean(str.substring(tag.length()+2).trim());;
							if(value) {
								if(tag.equals("Torus")) {
									which_region_checks[0].setSelected(true);
								} else if(tag.equals("Equatorial")) {
									which_region_checks[1].setSelected(true);
								} else if(tag.equals("Wind")) {
									which_region_checks[2].setSelected(true);
								} else if(tag.equals("Cone")) {
									which_region_checks[3].setSelected(true);
								} else if(tag.equals("Counter-cone")) {
									which_region_checks[4].setSelected(true);
								}
							}
						}
					} else if(!previous_region.equals("[End]")) {
						for(int i = 0; i < config_panels.length; i++) {
							if(previous_region.equals("[" + config_panels[i].getName() + "]")) {
								config_panels[i].loadConfiguration(entry_buffer);
							}
						}
					}
					entry_buffer = new ArrayList<String>();
				}
				
				if(!read.equals("")) {
					previous_region = read;
				}
				
			} else {
				entry_buffer.add(read);
			}
		}
		
		input.close();
	}
	
	/**
	 * creates the JMenuBar for this ConfigWindow
	 */
    private void initializeMenuBar() {
        JMenuBar menu_bar = new JMenuBar();
        config_window.setJMenuBar(menu_bar);
        JMenu file_menu = new JMenu("File");
        
		
		file_menu.setMnemonic(KeyEvent.VK_F);

		JMenuItem run_item = new JMenuItem("Run");
		JMenuItem choose_executable = new JMenuItem("Choose Executable");
		JMenuItem choose_output_path = new JMenuItem("Choose Output Directory");
        JMenuItem save_item = new JMenuItem("Save Configuraton");
        JMenuItem load_item = new JMenuItem("Load Configuration");
        JMenuItem exit_item = new JMenuItem("Exit");
        final JFileChooser file_chooser = new JFileChooser(".");

		save_item.setMnemonic(KeyEvent.VK_S);
		choose_output_path.setMnemonic(KeyEvent.VK_O);
		choose_executable.setMnemonic(KeyEvent.VK_E);
		load_item.setMnemonic(KeyEvent.VK_L);
		run_item.setMnemonic(KeyEvent.VK_R);
		exit_item.setMnemonic(KeyEvent.VK_X);
		
		
		choose_output_path.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				file_chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
				int choice = file_chooser.showOpenDialog(config_window);
				file_chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
				if(choice == JFileChooser.APPROVE_OPTION) {
					File dir = file_chooser.getSelectedFile();
					dir.mkdirs();
					directory = dir.getAbsolutePath() + System.getProperty("file.separator");
				}
			}
		});
		
		choose_executable.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int choice = file_chooser.showOpenDialog(config_window);
				if(choice == JFileChooser.APPROVE_OPTION) {
					File file = file_chooser.getSelectedFile();
					executable_name = file.getAbsolutePath();
				}
			}
		});
		
        save_item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int choice = file_chooser.showSaveDialog(config_window);
                if(choice == JFileChooser.APPROVE_OPTION) {
                    File file = file_chooser.getSelectedFile();
					try {
						saveConfiguration(file);
					} catch(IOException ioe) {
						error(ioe.getStackTrace().toString());
					}
                }
            }
        });
        load_item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                int choice = file_chooser.showOpenDialog(config_window);
                if(choice == JFileChooser.APPROVE_OPTION) {
                    File file = file_chooser.getSelectedFile();
					try {
						loadConfiguration(new FileInputStream(file));
					} catch(IOException ioe) {
						error(ioe.getStackTrace().toString());
					}
                }
            }
        });

		run_item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                new Thread() {
                    public void run() {
                        try {
							progress_bar.setValue(0);
							
							status_field.setText("Converting Field values...");
							doConversions();
							status_field.setText("Generating output files...");
							
							log_output = new PrintStream(directory + "files.txt");
							int files_total = 1;
							int this_panel_files = 1;
							String this_panel_name = null;
							Map<Field, Range> ranges = new HashMap<Field, Range>();
							for(RegionConfigPanel panel : config_panels) {
								panel.verifyFields();
								Map<Field, Range> temp = panel.fillRanges();
								if(main_config_pane.getSelectedComponent() == panel) {//compare memory locations
									this_panel_files = panel.getNumFilesToGenerate();
									this_panel_name = panel.getName();
								}
								files_total *= panel.getNumFilesToGenerate();
								ranges.putAll(temp);
							}
							total_files.setText(Integer.toString(files_total) + " total files to generate");
							files_this_panel.setText(Integer.toString(this_panel_files) + " combinations on " + this_panel_name + " panel");
							
							range_set = ranges.entrySet();
							LinkedList<Map.Entry<Field, Range>> ranged_fields = new LinkedList<Map.Entry<Field, Range>>(range_set);
							progress_bar.setMaximum(files_total);
							file_number = 0;
							File exe = new File(executable_name);
							if(exe.exists()) {
								if(files_total > 1) {
									generateFiles(ranged_fields);
								} else {
									String filename = directory + "windfundat.bin";
									generateOutputFile(new File(filename));
									
									//spawn the scattering program
									ProcessBuilder pb = new ProcessBuilder(executable_name);

									System.out.println("running: " + executable_name);
			                        Process proc = pb.start();
			                        try {
			                        	proc.waitFor();
			                        } catch(InterruptedException ie) {
			                        	
			                        }
								}
							} else {
								error("Executable file: \"" + executable_name + "\" does not exist");
							}
							progress_bar.setValue(files_total);
							status_field.setText("Done!");
						} catch(IOException ioe) {
                            ioe.printStackTrace(System.err);
                        } catch(IllegalArgumentException iae) {
                        	StringBuilder buf = new StringBuilder("");
                        	for(StackTraceElement ste : iae.getStackTrace()) {
                        		buf.append(ste.toString() + "\n");
                        	}
							error(iae.getMessage() + "\n" + buf.toString());
						} 
						if(progress_bar.isIndeterminate()) {
							progress_bar.setIndeterminate(false);
						}
                    }
                }.start();
            }
			
			private int file_number;
			private PrintStream log_output;
			private Set<Map.Entry<Field, Range>> range_set;
			
			/**
			 * recursively generates all files
			 * 
			 * @param ranged_fields
			 * @throws IOException
			 */
			private void generateFiles(LinkedList<Map.Entry<Field, Range>> ranged_fields) throws IOException {
				
				if(ranged_fields.size() == 0) {
					return;
				}
				Map.Entry<Field, Range> entry = ranged_fields.removeFirst();
				Field field = entry.getKey();
				Range range = entry.getValue();
				double lb = range.lower();
				double ub = range.upper();
				
				//ranged_fields.remove(entry);
				String name = field.getName();
				if(name.length() > 30) {
					name = name.substring(0, 30);
				}
				//System.out.println("Ranging field " + name);
				for(double i = lb; i <= ub; i = range.current()) {
					field.setValue(Double.toString(i));
					//System.out.println(name + " --> " + i);
					
					if(ranged_fields.size() == 0) {
						//if there are no more fields to range (this is the one at the bottom of the tree)
						//then all fields are set, and the output file for these parameters needs to be generated
						String filename = "windfundat.bin";
						File my_output_file = new File(filename);
						generateOutputFile(my_output_file);
						String windfun_number = "windfundat-" + file_number + ".bin";
						status_field.setText(filename + " created successfully!");
						
						log_output.println(windfun_number + "    wd_datout2-" + file_number + ".bin");
						for(Map.Entry<Field, Range> e : range_set) {
							Field f = e.getKey();
							log_output.println("  " + f.getParentPanel().getName() + " :: " + f.getName() + " " + f.getValue());
						}
						log_output.println();
						
						//Young's program outputs: wd_datout2
						
						status_field.setText("running " + executable_name + " on " + windfun_number);
						//spawn the scattering program
						ProcessBuilder pb = new ProcessBuilder(executable_name);

                        Process proc = pb.start();
                        try {
                        	proc.waitFor();
                        } catch(InterruptedException ie) {
                        	
                        }
						//perform renaming and file moving
						status_field.setText("renaming files...");
						my_output_file.renameTo(new File(directory + windfun_number));
						File windfun_output_file = new File("wd_datout2.bin");
						windfun_output_file.renameTo(new File(directory + "wd_datout2-" + file_number + ".bin"));
						
						status_field.setText("done with " + windfun_number);
						progress_bar.setValue(file_number);
						file_number++;
					} else {
						//there are still fields to recurse and range
						generateFiles(ranged_fields);
					}
					
					range.doStep();
				}
				range.resetCurrentToLower();
				field.setValue(Double.toString(range.lower()));
				ranged_fields.addFirst(entry);
			}
        });
		
		exit_item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				config_window.dispose();
				System.exit(0);
			}
		});
		
		JMenu option_menu = new JMenu("Options");
		option_menu.setMnemonic(KeyEvent.VK_O);
		
		JMenuItem update_item = new JMenuItem("Update");
		update_item.setMnemonic(KeyEvent.VK_U);
		
		update_item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				int files_total = 1;
				int this_panel_files = 1;
				String this_panel_name = null;
				for(RegionConfigPanel panel : config_panels) {
					/*Map<Field, Range> ranges = */panel.fillRanges();
					if(main_config_pane.getSelectedComponent() == panel) {//compare memory locations
						this_panel_files = panel.getNumFilesToGenerate();
						this_panel_name = panel.getName();
					}
					files_total *= panel.getNumFilesToGenerate();
				}
				total_files.setText(Integer.toString(files_total) + " total files to generate");
				files_this_panel.setText(Integer.toString(this_panel_files) + " combinations on " + this_panel_name + " panel");
			}
		});
		
		file_menu.add(run_item);
		file_menu.addSeparator();
		file_menu.add(choose_executable);
		file_menu.add(choose_output_path);
		file_menu.addSeparator();
        file_menu.add(save_item);
        file_menu.add(load_item);
        file_menu.addSeparator();
        file_menu.add(exit_item);
		
		option_menu.add(update_item);
		
        menu_bar.add(file_menu);
		menu_bar.add(option_menu);
    }

    /**
     * shows the ConfigWindow
     */
    public void show() {
    	config_window.setVisible(true);
    }
    
    /**
     * displays an error alert dialog box with the given message.
     * @param message The message to display
     */
	private void error(String message) {
		JOptionPane.showMessageDialog(config_window, message, "Error!", JOptionPane.ERROR_MESSAGE);
	}
}
